/**
 * \file Code.cpp
 * \brief Implementation of control-oriented Code subclasses
 * \author Wouter Caarls <w.caarls@tnw.tudelft.nl> 
 *
 * \verbatim
 * Copyright (c) 2005 Wouter Caarls, Delft University of Technology
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * \endverbatim
 */

#include <iostream>

#include "CStr.h"
#include "Base.h"
#include "Code.h"
#include "Expression.h"
#include "Value.h"
#include "Symbol.h"
#include "debug.h"

Code* Stat::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  plog.reduce() << HDR << this->toString();

  setLastStatement(this);

  if (!interactive)
    debugHook(this, symbols, out);

  out = out->spawn();
  symbols->newFrame();

  Expression *res;
  if (interactive)
  {
    res = data->reduce(symbols, out, AsExpression);
    std::cout << res->toString() << std::endl;
  }
  else
    res = data->reduce(symbols, out);

  if (res->hasSideEffects())
  {
    res->residualize(symbols);
    out->push_back_stat(new Stat(res));
  }
  else
    delete res;

  symbols->popFrame();

  return new VoidValue();
}

void Stat::residualize(SymbolTable *symbols)
{
  if (tentative)
  {
    tentative = false;

    plog.residualize() << HDR << "residualizing assignment " << toString() << std::endl;
  }

  data->residualize(symbols);
}

std::string Stat::toString(int indentation)
{
  if (tentative && !g_outputTentative)
    return "";

  return (char*)(CStr(indent(indentation)) << data->toString() << ";\n");
}

std::string Stat::toTerm()
{
  if (tentative)
    return "EmptyExp";
  else
    return (char*)(CStr("Stat(") << data->toTerm() << ")");
}

void Stat::clear(void)
{
  if (!refcount)
    error("Assignment reference count fell below zero");

  refcount--;

  if (!refcount)
  {
    bool outputTentative = g_outputTentative;
    g_outputTentative = true;

    plog.residualize() << HDR << "clearing assignment " << toString() << std::endl;

    g_outputTentative = outputTentative;

    delete data;
    data = new VoidValue();
  }
}

/* ************************************************************************* */

Code* TranslationUnit::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  for (TI ii=data.begin(); ii != data.end(); ++ii)
    delete (*ii)->reduce(symbols, out);

  return new VoidValue();
}

std::string TranslationUnit::toString(int indentation)
{
  CStr res;

  res << indent(indentation);

  for (TI ii=data.begin(); ii != data.end(); ++ii)
    res << (*ii)->toString();

  return (char*)res;
}

std::string TranslationUnit::toTerm()
{
  CStr res;

  if (data.empty())
    return "TranslationUnit([])";

  res << "TranslationUnit([";
  for (TI ii=data.begin(); ii != data.end(); ++ii)
  {
    res << (*ii)->toTerm();
    if (ii != data.end()-1)
      res << ", ";
  }
  res << "])";

  return (char*)res;
}

void TranslationUnit::push_back(Code *code)
{
  data.push_back(code);
}

/* ************************************************************************* */

Code* Compound::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  out = out->spawn();
  symbols->newFrame();

  Code *res = NULL;

  for (CI ii=decls.begin(); ii != decls.end(); ++ii)
    delete (*ii)->reduce(symbols, out);

  for (CI ii=stats.begin(); ii != stats.end(); ++ii)
  {
    res = (*ii)->reduce(symbols, out);

    if (typeid(*res) == typeid(Break))
      break;

    delete res;
    res = NULL;
  }

  symbols->popFrame();

  if (res)
    return res;
  else
    return new VoidValue();
}

std::string Compound::toString(int indentation)
{
  std::string ires;
  CStr res, dres, sres;
  int newindentation = indentation+1;
  int ndecls=0, nstats=0;

  if (!decls.empty())
    for (CI ii=decls.begin(); ii != decls.end(); ++ii)
    {
      ires = (*ii)->toString(newindentation);
      if (ires != "") ndecls++;
      dres << ires;
    }

  if (!stats.empty())
    for (CI ii=stats.begin(); ii != stats.end(); ++ii)
    {
      ires = (*ii)->toString(newindentation);
      if (ires != "") nstats++;
      sres << ires;
    }

  if (ndecls && nstats)
    res << dres.str() << "\n" << sres.str();
  else
    res << dres.str() << sres.str();

  if (ndecls || !automatic)
    return (char*)(CStr(indent(indentation)) << "{\n" << res.str() << indent(indentation)
		   << "}\n");
  else
    return (char*)res;
}

std::string Compound::toTerm()
{
  std::string ires;
  CStr res, dres, sres;
  int ndecls=0, nstats=0;

  if (!decls.empty())
    for (CI ii=decls.begin(); ii != decls.end(); ++ii)
    {
      ires = (*ii)->toTerm();
      ndecls++;

      dres << ires;
      if (ii != decls.end()-1)
	dres << ", ";
    }

  if (!stats.empty())
    for (CI ii=stats.begin(); ii != stats.end(); ++ii)
    {
      ires = (*ii)->toTerm();
      nstats++;

      sres << ires;
      if (ii != stats.end()-1)
	sres << ", ";
    }

  if (ndecls || !automatic)
    res << "Compound([" << dres.str() << "] , [" << sres.str() << "])";
  else if (nstats)
    /* *** ASSUMES AUTOMATIC COMPOUNDS ARE DIRECT CHILDREN OF A COMPOUND *** */
    res << sres.str();
  else
    res << "EmptyExp";
 
  return (char*)res;
}

void Compound::push_back_decl(Code *code)
{
  decls.push_back(code);
}

void Compound::push_back_stat(Code *code)
{
  Compound *compound = dynamic_cast<Compound*>(code);

  if (compound)
    compound->parent = this;

  stats.push_back(code);
}

void Compound::merge(Compound *compound)
{
  if (!compound->decls.empty())
    for (CI ii=compound->decls.begin(); ii != compound->decls.end(); ++ii)
      push_back_decl((*ii));

  /* Avoid deletion */
  compound->decls.clear();

  if (!compound->stats.empty())
    for (CI ii=compound->stats.begin(); ii != compound->stats.end(); ++ii)
      push_back_stat((*ii));  

  /* Avoid deletion */
  compound->stats.clear();

  delete compound;
}

/* ************************************************************************* */

Code* If::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  out = out->spawn();
  symbols->newFrame();

  plog.reduce() << HDR << "if (" << condition->toString() << ")" << std::endl;
  setLastStatement(this);

  if (!interactive)
    debugHook(this, symbols, out);

  Expression *rcond = condition->reduce(symbols, out, AsExpression);

  if (typeid(*rcond) == typeid(IntegerValue))
  {
    IntegerValue *icond = dynamic_cast<IntegerValue*>(rcond);

    if (icond->castToInteger())
    {
      delete icond;

      symbols->popFrame();
      return data->reduce(symbols, out);
    }
    else
    {
      delete icond;

      symbols->popFrame();
      return new VoidValue();
    }
  }

  plog.residualize() << HDR << "condition is unknown" << std::endl;

  SymbolTable *dsym = symbols->spawn();
  Compound *dout = new Compound();
  delete data->reduce(dsym, dout);

//  dsym->dirtyChanged(symbols);
  symbols->dirtyChanged(dsym);
  delete dsym;

  rcond->residualize(symbols);
  out->push_back_stat(new If(rcond, dout));

  symbols->popFrame();

  return new VoidValue();
}

std::string If::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "if (" << condition->toString() << ")\n"
		 << data->toString(indentation+1));
}

std::string If::toTerm()
{
  return (char*)(CStr("If(") << condition->toTerm() << ", " << data->toTerm() << ")");
}

/* ************************************************************************* */

Code* IfElse::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  out = out->spawn();
  symbols->newFrame();

  plog.reduce() << HDR << "if (" << condition->toString() << ")" << std::endl;
  setLastStatement(this);

  if (!interactive)
    debugHook(this, symbols, out);

  Expression *rcond = condition->reduce(symbols, out, AsExpression);

  if (typeid(*rcond) == typeid(IntegerValue))
  {
    IntegerValue *icond = dynamic_cast<IntegerValue*>(rcond);

    if (icond->castToInteger())
    {
      delete icond;

      symbols->popFrame();
      return truedata->reduce(symbols, out);
    }
    else
    {
      delete icond;

      symbols->popFrame();
      return falsedata->reduce(symbols, out);
    }
  }

  plog.residualize() << HDR << "condition is unknown" << std::endl;

  SymbolTable *tsym = symbols->spawn(), *fsym = symbols->spawn();

  Compound *tout = new Compound(), *fout = new Compound();
  delete truedata->reduce(tsym, tout);
//  tsym->dirtyChanged(symbols);

  plog.reduce() << HDR << "else" << std::endl;

  delete falsedata->reduce(fsym, fout);
//  fsym->dirtyChanged(symbols);

  symbols->dirtyChanged(tsym);
  symbols->dirtyChanged(fsym);

  delete tsym;
  delete fsym;

  rcond->residualize(symbols);
  out->push_back_stat(new IfElse(rcond, tout, fout));

  symbols->popFrame();

  return new VoidValue();
}

std::string IfElse::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "if (" << condition->toString() << ")\n"
		 << truedata->toString(indentation+1) << indent(indentation)
		 << "else\n" << falsedata->toString(indentation+1));
}

std::string IfElse::toTerm()
{
  return (char*)(CStr("IfElse(") << condition->toTerm() << ", " << truedata->toTerm()
		 << ", " << falsedata->toTerm() << ")");
}

/* ************************************************************************* */

Code* While::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  For *loop = new For(new VoidValue(), condition->copy(), new VoidValue(), data->copy());

  Code *res = loop->reduce(symbols, out);
  delete loop;

  return res;
}

std::string While::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "while (" << condition->toString() << ")\n"
		<< data->toString(indentation+1));
}

std::string While::toTerm()
{
  return (char*)(CStr("While(") << condition->toTerm() << ", " << data->toTerm() << ")");
}

/* ************************************************************************* */

Code* DoWhile::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  out = out->spawn();
  symbols->newFrame();

  Code *res = data->reduce(symbols, out);
    
  if (typeid(*res) == typeid(Break))
  {
    delete res;
    symbols->popFrame();
    return new VoidValue();    
  }

  delete res;

  symbols->popFrame();
  
  While *loop = new While(condition->copy(), data->copy());

  res = loop->reduce(symbols, out);
  delete loop;

  return res;  
}

std::string DoWhile::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "do\n" << data->toString(indentation+1)
		 << "while (" << condition->toString() << ")\n");
}

std::string DoWhile::toTerm()
{
  return (char*)(CStr("DoWhile(") << data->toTerm() << ", " << condition->toTerm() << ")");
}

/* ************************************************************************* */

Code* For::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  plog.reduce() << HDR << "for (" << initializer->toString() << "; "
		<< condition->toString() << "; " << increment->toString() << ")"
		<< std::endl;
  setLastStatement(this);

  if (!interactive)
    debugHook(this, symbols, out);

  out = out->spawn();
  symbols->newFrame();

  Compound *initout = new Compound(true);

  delete initializer->reduce(symbols, initout);

  int iterations=0;
  for (;;)
  {
    SymbolTable *csym = symbols->spawn();
    Compound *out2 = new Compound(true);

    plog.reduce() << HDR << condition->toString() << std::endl;
    Expression *rcond = condition->reduce(csym, out2, AsExpression);
    
    /* *** Ugly hack to avoid out-of-control loops. Should be based on whether
     * the loop generates code 
     */
    if (typeid(*rcond) == typeid(IntegerValue) &&
	(iterations < 32 || !dynamic_cast<IntegerValue*>(rcond)->castToInteger()))
    {
      delete csym;
      delete out2;
      delete rcond;

      if (initout)
      {
        out->merge(initout);
        initout = NULL;
      }

      rcond = condition->reduce(symbols, out, AsExpression);

      IntegerValue *icond = dynamic_cast<IntegerValue*>(rcond);

      if (icond->castToInteger())
      {
	delete icond;

	Code *res = data->reduce(symbols, out);

	if (typeid(*res) == typeid(Break))
	{	  
	  Break *brk = dynamic_cast<Break*>(res);

	  brk->removeGenerators();

	  delete res;
	  break;
	}

	delete res;
	delete increment->reduce(symbols, out);
      }
      else
      {
	delete icond;
	break;
      }
    }
    else
    {
      /* Don't know if the condition is true or not */
      plog.residualize() << HDR << "loop condition is unknown" << std::endl;

      delete rcond;

      SymbolTable *isym = symbols->spawn(), *dsym = symbols->spawn();

      Compound *dout = new Compound(), *cout = new Compound();
      Compound *incrout = new Compound();

      delete condition->reduce(csym, out2);
      delete increment->reduce(isym, out2);
      delete data->reduce(dsym, out2);

      symbols->dirtyChanged(csym, false);
      symbols->dirtyChanged(isym, false);
      symbols->dirtyChanged(dsym, false);

      delete csym;
      delete isym;
      delete dsym;
      delete out2;

      /* After determining which variables are set by the loop, reduce it again,
       * thereby taking into account all the variables that are unknown during
       * multiple iterations of the loop */
      csym = symbols->spawn(), isym = symbols->spawn(), dsym = symbols->spawn();
      
      rcond = condition->reduce(csym, cout, AsExpression);
      delete data->reduce(dsym, dout);
      delete increment->reduce(isym, incrout);

      symbols->dirtyChanged(csym);
      symbols->dirtyChanged(isym);
      symbols->dirtyChanged(dsym);

      delete csym;
      delete dsym;
      delete isym;

      rcond->residualize(symbols);
      dout->residualizeBreaks();

      /* Try to make it look as much like a for loop as possible.
       * This is necessary for some optimizing compilers
       * *cough* TriMedia *cough* */

      Expression *newinit, *newincr;

      /* Only create a new init if reduces to just one expression and the
       * condition doesn't have sideeffects */
      if (initout && initout->decls.empty() && initout->stats.size() == 1 &&
          typeid(*initout->stats[0]) == typeid(Stat) &&
	  cout->decls.empty() && cout->stats.empty())
      {
        newinit = ((Stat*)initout->stats[0])->data;
        initout->stats.clear();
        delete initout;
      }
      else
      {
        newinit = new VoidValue();
        if (initout)
          out->merge(initout);
      }

      /* Only create a new increment if it reduces to just one expression,
       * AND the condition doesn't have side effects. If the condition has
       * sideeffects, it is impossible to construct a proper increment
       * because of ordering issues. */
      if (incrout && incrout->decls.empty() && incrout->stats.size() == 1 &&
          typeid(*incrout->stats[0]) == typeid(Stat) &&
          cout->decls.empty() && cout->stats.empty())
      {
        newincr = ((Stat*)incrout->stats[0])->data;
        incrout->stats.clear();
        delete incrout;
      }
      else
      {
        newincr = new VoidValue();
        if (incrout)
          dout->merge(incrout);
      }

      dout->merge(cout->copy());
      out->merge(cout);
      out->push_back_stat(new For(newinit,
				  rcond, newincr, dout));
      break;
    }

    iterations++;
  } 

  symbols->popFrame();

  return new VoidValue();
}

std::string For::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "for (" << initializer->toString() << "; "
		 << condition->toString() << "; "
		 << increment->toString() << ")\n" << data->toString(indentation+1));
}

std::string For::toTerm()
{
  return (char*)(CStr("For(") << initializer->toTerm() << ", " << condition->toTerm()
		 << ", " << increment->toTerm() << ", " << data->toTerm() << ")");
}

/* ************************************************************************* */

Break* Return::reduce(SymbolTable *symbols, Compound *out, Use use)
{
  Id *id = symbols->findReturnVariable();
  
  if (id)
  {
    type = id->type;

    out = out->spawn();
    symbols->newFrame();
    
    Stat *stat = new Stat(new Assign(type, AssignEq, id, data->copy()));
    
    delete stat->reduce(symbols, out);
    
    symbols->popFrame();
    return new Break();
  }
  else
  {
    /* Don't know what we're exiting from */
    Expression *res = data->reduce(symbols, out, AsExpression);
    type = res->type;   

    /* *** SHOULD BE RETURN *** */
    return new Break();
  }
}

std::string Return::toString(int indentation)
{
  return (char*)(CStr(indent(indentation)) << "return " << data->toString() << ";\n");
}

std::string Return::toTerm()
{
  return (char*)(CStr("Return(") << data->toTerm() << ")");
}
